﻿using System;
using System.DirectoryServices;
using System.Linq;
using Contrib.Ldap.Models;
using JetBrains.Annotations;
using Orchard;
using Orchard.ContentManagement;
using Orchard.Localization;
using Orchard.Security;
using Orchard.UI.Notify;

namespace Contrib.Ldap.Services {
    [UsedImplicitly]
    public class LdapMembershipService : IMembershipService {
        private readonly IOrchardServices _orchardServices;

        public LdapMembershipService(IOrchardServices orchardServices) {
            _orchardServices = orchardServices;

            T = NullLocalizer.Instance;
        }

        public Localizer T { get; set; }

        /// <summary>
        /// The active directory domain.
        /// </summary>
        public string Domain { get; set; }

        /// <summary>
        /// Retrieves the membership settings.
        /// </summary>
        /// <returns>The membership settings.</returns>
        public MembershipSettings GetSettings() {
            var settings = new MembershipSettings();

            // accepting defaults
            return settings;
        }

        /// <summary>
        /// Retrieves an user based on its username.
        /// </summary>
        /// <param name="username">The username.</param>
        /// <returns>The user.</returns>
        public IUser GetUser(string username) {
            var lowerName = username == null ? "" : username.ToLower();

            return _orchardServices.ContentManager
                .Query<UserPart, UserPartRecord>()
                .Where(u => u.NormalizedUserName == lowerName)
                .List()
                .FirstOrDefault();
        }

        /// <summary>
        /// Validates an user account.
        /// </summary>
        /// <param name="username">The username.</param>
        /// <param name="password">The password.</param>
        /// <returns>The user if validation passes; null otherwise.</returns>
        public IUser ValidateUser(string username, string password) {
            if (string.IsNullOrEmpty(username)) {
                throw new ArgumentNullException("username");
            }

            if (string.IsNullOrEmpty(password)) {
                throw new ArgumentNullException("password");
            }

            if (string.IsNullOrEmpty(Domain)) {
                throw new ArgumentNullException("Domain");
            }

            try {
                using (new DirectoryEntry(Domain, username, password)) {

                    return _orchardServices.ContentManager
                        .Query<UserPart, UserPartRecord>()
                        .Where(user => user.NormalizedUserName == GetNormalizedUsername(username))
                        .List()
                        .FirstOrDefault();
                }
            }
            catch (Exception exception) {
                _orchardServices.Notifier.Add(NotifyType.Error, T("Login failed with message: {0}", exception.Message));

                // Authentication failed)
                return null;
            }
        }

        /// <summary>
        /// Creating an LDAP user through Orchard is not supported.
        /// </summary>
        /// <param name="createUserParams">The parameters to create the user.</param>
        /// <exception cref="NotSupportedException">Not supported.</exception>
        public IUser CreateUser(CreateUserParams createUserParams) {
            throw new NotSupportedException();
        }

        /// <summary>
        /// Changing an LDAP's user password through Orchard is not supported.
        /// </summary>
        /// <param name="user">The user to change password.</param>
        /// <param name="password">The target password.</param>
        /// <exception cref="NotSupportedException">Not supported.</exception>
        public void SetPassword(IUser user, string password) {
            throw new NotSupportedException();
        }

        private static string GetNormalizedUsername(string username) {
            return username == null ? string.Empty : username.ToLower();
        }
    }
}